package cn.kgc.security;

import cn.hutool.core.util.StrUtil;
import cn.kgc.entity.Users;
import cn.kgc.service.MenusService;
import cn.kgc.service.UsersService;
import cn.kgc.util.*;
import cn.kgc.util.cache.RedisUtil;
import cn.kgc.util.exception.HttpException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * 验证令牌的过滤器，保证一个请求过滤器此过滤器只执行一次
 */

public class TokenAuthenticationFilter extends OncePerRequestFilter {
    private final JwtUtil jwtUtil;
    private final RedisUtil redisUtil;
    private final UsersService usersService;
    private final MenusService menusService;


    public TokenAuthenticationFilter(JwtUtil jwtUtil, RedisUtil redisUtil,
                                     UsersService usersService, MenusService menusService) {
        this.jwtUtil = jwtUtil;
        this.redisUtil = redisUtil;
        this.usersService = usersService;
        this.menusService = menusService;
    }

    /**
     * 根据用户id获取用户信息 将用户封装为UsernamePasswordAuthenticationToken对象
     *
     * @param userId
     * @return
     */
    private UsernamePasswordAuthenticationToken getAuthentication(String userId) {
        //根据用户的id获得用户的信息
        Users users = usersService.getById(userId);
        //如果令牌对应的用户被删或者离职直接返回null
        if (users == null || users.getState() == SystemConstant.USER_STATE_RESIGN) {
            return null;
        }
        //根据用户id获得用户的权限信息
        //从缓存中获得用户的权限
        Object temp = redisUtil.get(SystemConstant.REDIS_PREFIX_SYS_AUTHORITY + users.getRoleId());
        List<SimpleGrantedAuthority> authorities = null;
        if (temp != null) {
            //如果缓存不为空直接获得缓存中的数据
            authorities = (List<SimpleGrantedAuthority>) temp;
        } else {
            authorities = menusService.getAuthorityByRoleId(users.getRoleId());
            //将权限信息放到缓存中
            redisUtil.set(SystemConstant.REDIS_PREFIX_SYS_AUTHORITY + users.getRoleId(),
                    authorities);
        }
        //将用户的信息和权限信息放到UsernamePasswordAuthenticationToken
        return new UsernamePasswordAuthenticationToken(users, null, authorities);
    }


    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if ("/users/login".equals(request.getRequestURI())) {
            //如果用户访问的地址是登录地址就直接换行
            filterChain.doFilter(request, response);
            return;
        }
        //用户访问的地址不是登录地址，验证令牌是否正确
        String token = request.getHeader("token");
        if (StrUtil.isBlank(token)) {
            ResponseUtil.setResponse(response,
                    Result.error(new HttpException(ResultConstant.LOGIN_ERROR))
            );
            return;
        }
        try {
            //如果令牌不为空，验证令牌是否正确
            String userId = jwtUtil.getUserId(token, false);
            //如果令牌正确，将当前登录的用户信息保存带SecurityContextHolder中
            //(如果将登录的用户信息保存到SecurityContextHolder中)
            UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(userId);
            if (authenticationToken == null) {
                //如果令牌对应的用户被禁用或者被删除返回登录的用户信息
                ResponseUtil.setResponse(response,
                        Result.error(new HttpException(ResultConstant.LOGIN_ERROR))
                );
                return;
            }

            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            //如果令牌错误(包括令牌过期),直接返回错误信息
            ResponseUtil.setResponse(response,
                    Result.error(new HttpException(ResultConstant.LOGIN_ERROR))
            );
        }
    }
}
